<?php

define('EL_EMERGENCY', 	1);
define('EL_ALERT', 		2);
define('EL_CRITICAL', 	4);
define('EL_ERROR', 		8);
define('EL_WARNING', 	16);
define('EL_NOTICE', 		32);
define('EL_INFO', 		64);
define('EL_DEBUG', 		128);

class Eventageous {
	public static $session = NULL;

	// how often logs will be checked for rotation... 10 means rand(1,10)
	public static $rotate_freq = 10;
	
	// max size for a log file before it is rotated
	public static $log_maxsize = 104857600; // 100mb in bytes
	// max age before an archived log is deleted
	public static $log_maxage = 31536000; // 1 year in seconds

	public static $dateFormat = 'Y-m-d H:i:s.u';
	
	// does the log file get opened once, and kept open? or open/closed each time
	public static $persistent = true;
	
	protected static $labels = array(
		EL_EMERGENCY 	=> 'Emergency'
		,EL_ALERT 		=> 'Alert'
		,EL_CRITICAL 	=> 'Critical'
		,EL_ERROR 		=> 'Error'
		,EL_WARNING 	=> 'Warning'
		,EL_NOTICE 		=> 'Notice'
		,EL_INFO 		=> 'Info'
		,EL_DEBUG 		=> 'Debug'
	);
	
	protected static $html_labels = array(
		EL_EMERGENCY 	=> '<span style="background:red url(icos/bomb.png) no-repeat left 2px; padding-left:20px; color:#fff;">Emergency</span>'
		,EL_ALERT 		=> '<span style="background:#fff url(icos/exclamation.png) no-repeat left 2px; padding-left:20px; color:red; text-decoration:underline;">Alert</span>'
		,EL_CRITICAL 	=> '<span style="background:#fff url(icos/exclamation.png) no-repeat left 2px; padding-left:20px; color:red; text-decoration:underline;">Critical</span>'
		,EL_ERROR 		=> '<span style="background:#fff url(icos/error.png) no-repeat left 2px; padding-left:20px; color:red;">Error</span>'
		,EL_WARNING 	=> '<span style="background:#fff url(icos/lightning.png) no-repeat left 2px; padding-left:20px;  color:orange;">Warning</span>'
		,EL_NOTICE 		=> '<span style="background:#fff url(icos/page_white_wrench.png) no-repeat left 2px; padding-left:20px; color:#000;">Notice</span>'
		,EL_INFO 		=> '<span style="background:#fff url(icos/information.png) no-repeat left 2px; padding-left:20px; color:#666;">Info</span>'
		,EL_DEBUG 		=> '<span style="background:#fff url(icos/comment.png) no-repeat left 2px; padding-left:20px; color:#999;">Debug</span>'
	);
	
	public static $logcsv_delimiter = ',';
	public static $logcsv_enclosure = '"';

	protected static $log = NULL;
	protected static $log_name = NULL;
	protected static $log_path = '.';
	protected static $log_handle = NULL;
	
	// the types of content that gets written to the disk log
	protected static $disk_loglevel = EL_DEBUG;
	// the types of content that gets output to the screen (if debug is set to true)
	protected static $output_loglevel = EL_DEBUG;
	
	protected static $output_template = "<span class=\"eventageous-logline\">%s</span><br />\n";
	
	// if output is true, then the data is output to the screen as well as logged to the file
	public static $output = true;
	
	// if disklog is true, then the data is saved to disk
	public static $disklog = true;
	
	private static $log_open = false;
	private static $path = NULL;
	
	public static function set_log($str='default.txt', $path=false) 
	{
		self::$path = dirname(__FILE__);
		if ( is_null( self::$session ) )
			self::$session = uniqid();
	
		if ( false !== $path )
			self::$log_path = $path;
		
		self::$log_name = $str;
		self::$log = self::$log_path . '/' . $str;
		
		if ( ! file_exists(self::$log) ) 
			self::init();

		if ( rand( 1, self::$rotate_freq ) == 1 ) 
			self::rotate_logs();
		
		if ( self::$persistent )
			self::open();
	}
	
	public static function init() 
	{
		if ( false === touch(self::$log) )
			throw new Exception('Unable to create log');
	}
	
	public static function open() 
	{
		self::$log_handle = fopen(self::$log, 'a');
		self::$log_open = true;
	}
	
	public static function close() 
	{
		if ( self::$log_open )
			fclose(self::$log_handle);
	}
	
	public static function log($logdata, $description=NULL, $tags=array(), $namespace='default', $priority=EL_INFO) 
	{
		$data = array(
			'date' 			=> self::get_date( self::$dateFormat )
			,'session' 		=> self::$session
			,'data' 			=> ( is_object($logdata) || is_array($logdata) ? print_r($logdata, true) : $logdata )
			,'description' => ( is_object($description) || is_array($description) ? print_r($description, true) : $description )
			,'tags' 			=> implode(',', $tags)
			,'namespace' 	=> $namespace
			,'priority' 	=> $priority
		);

		$EV = false;

		$eventclass = 'Eventageous_' . self::$labels[$priority];
		if ( class_exists( $eventclass ) ) // custom event?
		{
			$EV = new $eventclass($data);
			$data = $EV->modifyData($data);
		}
		elseif ( class_exists('Eventageous_Default') ) // oterhwise, default event?
		{
			$EV = new $eventclass($data);
			$data = $EV->modifyData($data);
		}
		
		$output = self::$output;
		if ( false !== $EV )
			$output = $EV->shouldOutput(self::$output);
		
		if ( $output && self::$output_loglevel >= $priority ) 
		{
			$line = sprintf( self::$output_template, ( $data['date'] . ' '. self::$html_labels[$priority] . ' (' . $namespace . '): ' . $logdata ) );
			if ( false !== $EV )
				$line = $EV->modifyOutput($line);

			echo $line;
		}

		$logdisk = self::$disklog;
		if ( false !== $EV )
			$logdisk = $EV->shouldLog($logdisk);

		if ( $logdisk && self::$disk_loglevel >= $priority )
		{
			if ( ! self::$persistent ) self::open();
	
			flock( self::$log_handle, LOCK_EX );
			fputcsv( self::$log_handle, $data, self::$logcsv_delimiter, self::$logcsv_enclosure );
			flock( self::$log_handle, LOCK_UN );
			
			if ( ! self::$persistent ) self::close();
		}
		
		if ( false !== $EV )
			$EV->fireEvent($data);
	}
	
	public static function emergency($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_EMERGENCY);
	}

	public static function alert($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_ALERT);
	}

	public static function critical($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_CRITICAL);
	}

	public static function error($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_ERROR);
	}

	public static function warning($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_WARNING);
	}

	public static function notice($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_NOTICE);
	}

	public static function info($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_INFO);
	}

	public static function debug($logdata, $description=NULL, $tags=array(), $namespace='default')
	{
		self::log($logdata, $description, $tags, $namespace, EL_DEBUG);
	}
	
	public static function log2($arr)
	{
		$vals = array('data', 'description', 'tags', 'namespace', 'priority');
		foreach ( $vals as $v )
			if ( ! isset( $arr[$v] ) )
				$arr[$v] = NULL;
	
		self::log( $arr['data'], $arr['description'], $arr['tags'], $arr['namespace'], $arr['priority'] );
	}
	
	protected static function rotate_logs() 
	{
		if( filesize( self::$log ) < self::$log_maxsize ) return false; // file hasn't hit threshold yet
		
		self::debug('Rotating logs', NULL, array(), 'eventageous.logrotation');
		
		$highest_idx = 0;
		
		$d = dir(self::$log_path);
		while ( false !== ( $entry = $d->read() ) ) 
		{
			$fn = self::$log_path . '/' . $entry;
			if ( $entry == '.' || $entry == '..' || is_dir($fn) ) continue;

			if ( filemtime($fn) < ( time() - self::$log_maxage ) ) 
			{
				self::debug( ('Removing aged file: '.$fn), NULL, array(), 'eventageous.logrotation' );
				unlink($fn);
			}
			
			list($log_name, $index) = explode('-', $entry, 2);
			if ( $log_name == self::$log_name && $index > $highest_idx )
				$highest_idx = $index;
		}
		$d->close();

		self::debug( ('Highest index is: '.$highest_idx), NULL, array(), 'eventageous.logrotation' );
		
		if ( $highest_idx > 0 ) 
		{
			self::debug( 'Renaming stack', NULL, array(), 'eventageous.logrotation' );
			for ( $i = $highest_idx; $i > 0; $i-- ) 
			{
				$fn = self::$log_path . '/' . self::$log_name . '-' . $i;
				if ( ! file_exists($fn) ) continue;

				$nfn = self::$log_path . '/' . self::$log_name . '-' . ($i+1);
				self::debug( ('Renaming "'.$fn.'" to "'.$nfn.'"'), NULL, array(), 'eventageous.logrotation' );
				rename($fn, $nfn);
			}
		}
		
		self::debug( 'Renaming root file', NULL, array(), 'eventageous.logrotation' );
		if ( file_exists( self::$log ) )
			rename( self::$log, ( self::$log_path . '/' . self::$log_name . '-1' ) );

		self::init(); // create log
	}
	
	public static function get_date($format, $utimestamp=NULL) 
	{
		/*
		// according to docs, "u" (microseconds) should be supported by date >= 5.2.2 but I can't get it to work... maybe I'm incompetent?
		if ( version_compare(PHP_VERSION, '5.2.2', '>=') )
			return date($format);
		*/
	
		 if ( is_null($utimestamp) )
			  $utimestamp = microtime(true);
	
		 $timestamp = floor($utimestamp);
		 $milliseconds = round( ($utimestamp - $timestamp) * 1000000 );
	
		 return date( preg_replace('#(?<!\\\\)u#', $milliseconds, $format), $timestamp);
	}
}

?>